package fr.cephb.joperon.core;

import java.util.Arrays;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import com.sleepycat.db.DatabaseEntry;

import fr.cephb.joperon.core.bio.Chromosome;
import fr.cephb.joperon.core.bio.ChromosomePosition;
import fr.cephb.joperon.core.bio.Organism;
import fr.cephb.joperon.core.bio.Positionable;
import fr.cephb.joperon.core.entities.XMLWritable;
/**
 * A wrapper over Mario Foglio's way to store the information Organism/Chromosome/position
 * The orginal information is stored in a char*
 * @author lindenb
 *
 */
@XmlRootElement(name="fid")
public class Fid
implements Comparable<Fid>,Positionable,XMLWritable
{
/** max length of the char* fid */
public final static int FID_MAX_LENGTH=16;
/** maximum position that can be stored in the fid */
public final static int MAX_VALUE=999999999;
private byte fid[];

protected Fid()
	{
	/** empty constructor required for XML marshalling */
	}

/** decode a Fid from an entry */
public Fid(DatabaseEntry entry)
	{
	this(entry.getData(),entry.getSize());
	}

/** decode the fid from a String */
public Fid(String s)
	{
	this(s.getBytes());
	}

/** decode the fid from an array of bytes */
public Fid(byte fid[])
	{
	this(fid,fid.length);
	}

/** decode the fid from an array of bytes */
public Fid(byte fid[],int len)
	{
	this();
	if(fid.length<len) throw new IndexOutOfBoundsException();
	this.fid=null;
	if(len>1 && fid[len-1]==0)//\0 at the end
		{
		this.fid=new byte[len-1];
		}
	else
		{
		this.fid=new byte[len];
		}
	System.arraycopy(fid, 0, this.fid, 0, this.fid.length);
	}

/** create a fid from an Organsime/Chromosome/Position */
public Fid(Organism org,Chromosome chrom,int position)
	{
	this();
	this.fid= new byte[FID_MAX_LENGTH-1];
	this.fid[0]='1';
	String s= String.valueOf( org.getOperonId() );
	if(s.length()==2)
		{
		this.fid[1]=(byte)s.charAt(0);
		this.fid[2]=(byte)s.charAt(1);
		}
	else
		{
		this.fid[1]='0';
		this.fid[2]=(byte)s.charAt(0);
		}
	s= String.valueOf( chrom.getId());
	if(s.length()==2)
		{
		this.fid[3]=(byte)s.charAt(0);
		this.fid[4]=(byte)s.charAt(1);
		}
	else
		{
		this.fid[3]='0';
		this.fid[4]=(byte)s.charAt(0);
		}
	this.fid[5]='1';
	s= String.valueOf(position);
	while(s.length()< (FID_MAX_LENGTH-1)-6)
		{
		s="0"+s;
		}
	for(int i=0;i< s.length();++i)
		{
		this.fid[6+i]=(byte)s.charAt(i);
		}
	}

/** return wether a fid in a char* is valid */
public static final boolean isValid(byte fid[])
	{
	int i=0;
	while(i< fid.length && i< FID_MAX_LENGTH && Character.isDigit(fid[i]) )
		{
		i++;
		}
	return i+1==FID_MAX_LENGTH;
	}

/** return if this fid is valid */
public boolean isValid()
	{
	return Fid.isValid(this.fid);
	}

/** extract the organism of this Fid */
public Organism getOrganism()
	{
	if(!isValid()) return Organism.UndefinedOrganism;
	return Organism.getByOperonId(Integer.parseInt(new String(fid,1,2)));
	}

/** extract the Chromosome of this Fid */
public Chromosome getChromosome()
	{
	return getChromosomePosition().getChromosome();
	}

/** extract the getChromosomePosition of this Fid */
public ChromosomePosition getChromosomePosition()
	{
	if(!isValid(fid))
		{
		return new ChromosomePosition(Chromosome.ChrUndefined,-1);
		}

	Chromosome chr;
	
	String c;
	if(fid[3]=='0')
		{
		c=new String(fid,4,1);
		}
	else
		{
		c=new String(fid,3,2);
		}


	chr = Chromosome.getById(Integer.valueOf(c));

	if(chr == Chromosome.ChrUndefined)
		{
		return new ChromosomePosition(Chromosome.ChrUndefined,-1);
		}
	
	int i = 6;
	int position=-1;
    while(fid[i] == '0' &&
    			i+1 < FID_MAX_LENGTH && //+1 car si on donne une 
    			i+1< fid.length) i++; //position ==0 on se retrouve avec un out-of-bound
    if (Character.isDigit(fid[i]))
    	{
    	position= Integer.parseInt(new String(fid,i,fid.length-i));
    	}
		
	return new ChromosomePosition(chr,position);
	}

/** extract the psoition of this Fid */
public int getPosition()
	{
	return getChromosomePosition().getPosition();
	}

/** return encode this fid as a char* */
public String asString()
	{
	return new String(this.fid);
	}

/** alias of asString , created for compatibility with the JAXB API*/
@XmlAttribute(name="value")
public final String getString()
	{
	return asString();
	}


/** convert it  to C string */
public byte[] as0EndedByteArray()
	{
	byte array[]=new byte[this.fid.length+1];
	System.arraycopy(this.fid, 0, array, 0, this.fid.length);
	array[fid.length]=0;
	return array;
	}



@Override
public int hashCode() {
	return Arrays.hashCode(this.fid);
	}

@Override
public boolean equals(Object obj)
	{
	if(this==obj) return true;
	if(obj==null || getClass()!=obj.getClass()) return false;
	return Arrays.equals(this.fid, Fid.class.cast(obj).fid);
	}
/** compare by organisme/chrom/position */
@Override
public int compareTo(Fid o) {
	int i= getOrganism().compareTo(o.getOrganism());
	if(i!=0) return i;
	return getChromosomePosition().compareTo(o.getChromosomePosition());
	}
@Override
public String toString() {
	return "{organism:"+getOrganism()+",chrom:"+getChromosome()+",pos:"+getPosition()+"}";
	}

@Override
public void writeXML(XMLStreamWriter out) throws XMLStreamException {
	out.writeStartElement("fid");
	out.writeCharacters(asString());
	out.writeEndElement();
	}


public static void main(String[] args)
	{
	try {
		Fid fid1= new Fid("101011000000691");
		System.err.println(fid1);
		Fid fid2= new Fid(Organism.Homo_sapiens,Chromosome.Chr1,691);
		System.err.println(fid1.asString());
		System.err.println(fid2.asString());
		System.err.println(fid2);
		System.err.println(Fid.isValid(fid2.fid));
		System.err.println(fid1.equals(fid2));
		
		JAXBContext context = JAXBContext.newInstance(fid1.getClass());
		Marshaller marshaller = context.createMarshaller();
		marshaller.marshal(fid1, System.out);
	} catch (Exception e) {
		e.printStackTrace();
		}	
	}

}
